﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace FluentParser.Expressions.Rules.Composition
{
    public enum GroupingOrientation
    {
        LeftToRight,
        RightToLeft
    }

    /// <summary>
    /// This special rule has the potential to parse a long serie of expressions.
    /// It reads an alternation of a value and operator in the goal of combining all the
    /// values using those operators. (Value [Operator Value]*)
    /// </summary>
    /// <typeparam name="TSource">The type of the source value</typeparam>
    /// <example><code>
    /// var number = ParseString.Regex("[0-9]+").Convert(Int32.Parse);
    /// Symbol spaces = ParseSymbol.Regex("[ \t\r\n]*");
    /// 
    /// var mathExpression = ParseOperators(number, spaces)
    ///     .Register("ADD", (x, y) => x + y)
    ///     .Register("SUB", (x, y) => x - y)
    ///     .Register("MULT", (x, y) => x * y)
    ///     .Register("DIV", (x, y) => x / y)
    ///     .Group(GroupingOrientation.LeftToRight, "MULT", "DIV")
    ///     .Group(GroupingOrientation.LeftToRight, "ADD", "SUB");
    /// 
    /// mathExpression.Parse("4 + 2 * 3") --> 10
    /// </code></example>
    public class OperatorsExpressionRule<TSource> : Expr<TSource> 
    {
        class ParsingTuple
        {
            public Operator Operator;
            public TSource Value;
            public int End;
        }

        class Operator
        {
            public Operator(string name, Symbol expression, Func<TSource, TSource, TSource> combine)
            {
                Name = name;
                Expression = expression;
                Combine = combine;
            }

            public string Name { get; private set; }
            public Symbol Expression { get; private set; }
            public Func<TSource, TSource, TSource> Combine { get; private set;  }
        }

        class OperatorGroup
        {
            public List<Operator> Operators { get; private set; }
            public GroupingOrientation Orientation { get; private set; }

            public OperatorGroup(GroupingOrientation orientation)
            {
                Orientation = orientation;
                Operators = new List<Operator>();
            }
        }

        public Expr<TSource> Source { get; private set; }
        public Symbol Separator { get; private set; }
        private readonly List<Operator> _operators;
        private readonly List<OperatorGroup> _groups;
        private readonly Dictionary<string, Operator> _operatorsByName;

        public OperatorsExpressionRule(Expr<TSource> source, Symbol separator)
        {
            Source = source;
            Separator = separator;
            _operators = new List<Operator>();
            _groups = new List<OperatorGroup>();
            _operatorsByName = new Dictionary<string, Operator>();
            TypeOperation = OperationType.Repetition;
        }

        /// <summary>
        /// Add a new operator separating two values
        /// </summary>
        /// <param name="name">Name of the operation later used for grouping</param>
        /// <param name="symbol">Symbol used for parsing the operator</param>
        /// <param name="combine">Function describing how to combine two results into one</param>
        /// <returns>Return the original object (see Builder Pattern)</returns>
        public OperatorsExpressionRule<TSource> Register(string name, Symbol symbol, Func<TSource, TSource, TSource> combine)
        {
            var @operator = new Operator(name, symbol, combine);
            _operators.Add(@operator);
            _operatorsByName.Add(name, @operator);
            return this;
        }

        /// <summary>
        /// Register one phase of grouping. Priority goes to the first group registered.
        /// </summary>
        /// <param name="orientation">Indicate if the processing has to be from left to right or right to left</param>
        /// <param name="operatorNames">Names of all the operators to process during this phase</param>
        /// <returns></returns>
        public OperatorsExpressionRule<TSource> Group(GroupingOrientation orientation, params string[] operatorNames)
        {
            OperatorGroup group = new OperatorGroup(orientation);
            foreach (string operatorName in operatorNames)
                group.Operators.Add(_operatorsByName[operatorName]);

            _groups.Add(group);

            return this;
        }

        #region Regular parsing

        public override ParsingResult<TSource> Parse(GrammarContext context, int index)
        {
            
            ParsingResult<TSource> expressionResult = Source.Parse(context, index);

            if (expressionResult != null)
            {
                List<ParsingTuple> tuples = new List<ParsingTuple>();
                ParsingTuple parsing = new ParsingTuple {End = expressionResult.End, Value = expressionResult.Value};
                do
                {
                    tuples.Add(parsing);
                    parsing = TryParseTuple(context, parsing.End);
                } while (parsing != null);

                foreach (OperatorGroup @group in _groups)
                {
                    switch(@group.Orientation)
                    {
                        case GroupingOrientation.LeftToRight:
                            for (int i = 1; i < tuples.Count;)
                                if (!TryGroup(@group, tuples, i))
                                    i++;
                            break;
                        case GroupingOrientation.RightToLeft:
                            for (int i = tuples.Count - 1; i > 0; i--)
                                TryGroup(@group, tuples, i);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }

                ParsingTuple result = tuples.Single();
                return new ParsingResult<TSource>(index, result.End - index, result.Value);
            }

            return null;
        }


        private static bool TryGroup(OperatorGroup group, List<ParsingTuple> tuples, int index)
        {
            ParsingTuple item = tuples[index];
            if (group.Operators.Contains(item.Operator))
            {
                ParsingTuple previous = tuples[index - 1];
                TSource newValue = item.Operator.Combine(previous.Value, item.Value);
                tuples.RemoveAt(index);
                tuples[index - 1] = new ParsingTuple { Value = newValue, End = item.End, Operator = previous.Operator };
                return true;
            }

            return false;
        }

        private ParsingTuple TryParseTuple(GrammarContext context, int index)
        {
            ParsingResult<object> resultSeparator1 = Separator.Parse(context, index);
            if (resultSeparator1 != null)
            {
                foreach (Operator op in _operators)
                {
                    ParsingResult<object> resultOperator = op.Expression.Parse(context, resultSeparator1.End);
                    if (resultOperator != null)
                    {
                        ParsingResult<object> resultSeparator2 = Separator.Parse(context, resultOperator.End);
                        if (resultSeparator2 != null)
                        {
                            ParsingResult<TSource> resultExpression = Source.Parse(context, resultSeparator2.End);
                            if (resultExpression != null)
                            {
                                return new ParsingTuple
                                           {
                                               End = resultExpression.End,
                                               Operator = op,
                                               Value = resultExpression.Value
                                           };
                            }
                        }
                    }
                }
            }

            return null;
        }

        #endregion Regular parsing

        #region Tree parsing

        public override void ParseNode(GrammarContext context, ParsingNodeResult node)
        {
            ParsingNodeResult expressionValue = node.AddChild(Source, context);
            if (expressionValue.IsSuccess)
            {
                List<ParsingTuple> tuples = new List<ParsingTuple>();
                ParsingTuple parsing = new ParsingTuple { End = expressionValue.End, Value = (TSource)expressionValue.Value };
                do
                {
                    tuples.Add(parsing);
                    parsing = NodeTryParseTuple(node, context, parsing.End);
                } while (parsing != null);

                foreach (OperatorGroup @group in _groups)
                {
                    switch (@group.Orientation)
                    {
                        case GroupingOrientation.LeftToRight:
                            for (int i = 1; i < tuples.Count; )
                                if (!TryGroup(@group, tuples, i))
                                    i++;
                            break;
                        case GroupingOrientation.RightToLeft:
                            for (int i = tuples.Count - 1; i > 0; i--)
                                TryGroup(@group, tuples, i);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }

                node.Value = tuples.Single().Value;
                node.IsSuccess = true;
            }
        }

        private ParsingTuple NodeTryParseTuple(ParsingNodeResult node, GrammarContext context, int index)
        {
            ParsingNodeResult valueSeparator1 = node.AddChild(Separator, context, index);
            if (valueSeparator1.IsSuccess)
            {
                foreach (Operator op in _operators)
                {
                    ParsingNodeResult valueOperator = node.AddChild(op.Expression, context, valueSeparator1.End);
                    if (valueOperator.IsSuccess)
                    {
                        ParsingNodeResult valueSeparator2 = node.AddChild(Separator, context, valueOperator.End);
                        if (valueSeparator2.IsSuccess)
                        {
                            ParsingNodeResult valueExpression = node.AddChild(Source, context, valueSeparator2.End);
                            if (valueExpression.IsSuccess)
                            {
                                return new ParsingTuple
                                           {
                                               End = valueExpression.End,
                                               Operator = op,
                                               Value = (TSource) valueExpression.Value,
                                           };
                            }
                        }
                    }
                }
            }

            node.End = index;
            return null;
        }

        #endregion Tree parsing
    }
}